// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_WASM_WASM_MODULE_H_
#define V8_WASM_WASM_MODULE_H_

#include <memory>

#include "src/globals.h"
#include "src/handles.h"
#include "src/vector.h"
#include "src/wasm/signature-map.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-opcodes.h"

namespace v8 {
namespace internal {

    class WasmDebugInfo;
    class WasmModuleObject;

    namespace wasm {

        using WasmName = Vector<const char>;

        class ErrorThrower;

        // Reference to a string in the wire bytes.
        class WireBytesRef {
        public:
            WireBytesRef()
                : WireBytesRef(0, 0)
            {
            }
            WireBytesRef(uint32_t offset, uint32_t length)
                : offset_(offset)
                , length_(length)
            {
                DCHECK_IMPLIES(offset_ == 0, length_ == 0);
                DCHECK_LE(offset_, offset_ + length_); // no uint32_t overflow.
            }

            uint32_t offset() const { return offset_; }
            uint32_t length() const { return length_; }
            uint32_t end_offset() const { return offset_ + length_; }
            bool is_empty() const { return length_ == 0; }
            bool is_set() const { return offset_ != 0; }

        private:
            uint32_t offset_;
            uint32_t length_;
        };

        // Static representation of a wasm function.
        struct WasmFunction {
            FunctionSig* sig; // signature of the function.
            uint32_t func_index; // index into the function table.
            uint32_t sig_index; // index into the signature table.
            WireBytesRef code; // code of this function.
            bool imported;
            bool exported;
        };

        // Static representation of a wasm global variable.
        struct WasmGlobal {
            ValueType type; // type of the global.
            bool mutability; // {true} if mutable.
            WasmInitExpr init; // the initialization expression of the global.
            union {
                uint32_t index; // index of imported mutable global.
                uint32_t offset; // offset into global memory (if not imported & mutable).
            };
            bool imported; // true if imported.
            bool exported; // true if exported.
        };

        // Note: An exception signature only uses the params portion of a
        // function signature.
        using WasmExceptionSig = FunctionSig;

        // Static representation of a wasm exception type.
        struct WasmException {
            explicit WasmException(const WasmExceptionSig* sig)
                : sig(sig)
            {
            }
            FunctionSig* ToFunctionSig() const { return const_cast<FunctionSig*>(sig); }

            const WasmExceptionSig* sig; // type signature of the exception.
        };

        // Static representation of a wasm data segment.
        struct WasmDataSegment {
            // Construct an active segment.
            explicit WasmDataSegment(WasmInitExpr dest_addr)
                : dest_addr(dest_addr)
                , active(true)
            {
            }

            // Construct a passive segment, which has no dest_addr.
            WasmDataSegment()
                : active(false)
            {
            }

            WasmInitExpr dest_addr; // destination memory address of the data.
            WireBytesRef source; // start offset in the module bytes.
            bool active = true; // true if copied automatically during instantiation.
        };

        // Static representation of a wasm indirect call table.
        struct WasmTable {
            MOVE_ONLY_WITH_DEFAULT_CONSTRUCTORS(WasmTable);
            ValueType type = kWasmStmt; // table type.
            uint32_t initial_size = 0; // initial table size.
            uint32_t maximum_size = 0; // maximum table size.
            bool has_maximum_size = false; // true if there is a maximum size.
            bool imported = false; // true if imported.
            bool exported = false; // true if exported.
        };

        // Static representation of wasm element segment (table initializer).
        struct WasmElemSegment {
            MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(WasmElemSegment);

            // Construct an active segment.
            WasmElemSegment(uint32_t table_index, WasmInitExpr offset)
                : table_index(table_index)
                , offset(offset)
                , active(true)
            {
            }

            // Construct a passive segment, which has no table index or offset.
            WasmElemSegment()
                : table_index(0)
                , active(false)
            {
            }

            // Used in the {entries} vector to represent a `ref.null` entry in a passive
            // segment.
            V8_EXPORT_PRIVATE static const uint32_t kNullIndex = ~0u;

            uint32_t table_index;
            WasmInitExpr offset;
            std::vector<uint32_t> entries;
            bool active; // true if copied automatically during instantiation.
        };

        // Static representation of a wasm import.
        struct WasmImport {
            WireBytesRef module_name; // module name.
            WireBytesRef field_name; // import name.
            ImportExportKindCode kind; // kind of the import.
            uint32_t index; // index into the respective space.
        };

        // Static representation of a wasm export.
        struct WasmExport {
            WireBytesRef name; // exported name.
            ImportExportKindCode kind; // kind of the export.
            uint32_t index; // index into the respective space.
        };

        enum class WasmCompilationHintStrategy : uint8_t {
            kDefault = 0,
            kLazy = 1,
            kEager = 2,
        };

        enum class WasmCompilationHintTier : uint8_t {
            kDefault = 0,
            kInterpreter = 1,
            kBaseline = 2,
            kOptimized = 3,
        };

        // Static representation of a wasm compilation hint
        struct WasmCompilationHint {
            WasmCompilationHintStrategy strategy;
            WasmCompilationHintTier baseline_tier;
            WasmCompilationHintTier top_tier;
        };

        enum ModuleOrigin : uint8_t { kWasmOrigin,
            kAsmJsOrigin };

#define SELECT_WASM_COUNTER(counters, origin, prefix, suffix)       \
    ((origin) == kWasmOrigin ? (counters)->prefix##_wasm_##suffix() \
                             : (counters)->prefix##_asm_##suffix())

        struct ModuleWireBytes;

        // Static representation of a module.
        struct V8_EXPORT_PRIVATE WasmModule {
            MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(WasmModule);

            std::unique_ptr<Zone> signature_zone;
            uint32_t initial_pages = 0; // initial size of the memory in 64k pages
            uint32_t maximum_pages = 0; // maximum size of the memory in 64k pages
            bool has_shared_memory = false; // true if memory is a SharedArrayBuffer
            bool has_maximum_pages = false; // true if there is a maximum memory size
            bool has_memory = false; // true if the memory was defined or imported
            bool mem_export = false; // true if the memory is exported
            int start_function_index = -1; // start function, >= 0 if any

            std::vector<WasmGlobal> globals;
            // Size of the buffer required for all globals that are not imported and
            // mutable.
            uint32_t untagged_globals_buffer_size = 0;
            uint32_t tagged_globals_buffer_size = 0;
            uint32_t num_imported_mutable_globals = 0;
            uint32_t num_imported_functions = 0;
            uint32_t num_imported_tables = 0;
            uint32_t num_declared_functions = 0; // excluding imported
            uint32_t num_exported_functions = 0;
            uint32_t num_declared_data_segments = 0; // From the DataCount section.
            uint32_t num_lazy_compilation_hints = 0; // From compilation hints section.
            WireBytesRef name = { 0, 0 };
            std::vector<FunctionSig*> signatures; // by signature index
            std::vector<uint32_t> signature_ids; // by signature index
            std::vector<WasmFunction> functions;
            std::vector<WasmDataSegment> data_segments;
            std::vector<WasmTable> tables;
            std::vector<WasmImport> import_table;
            std::vector<WasmExport> export_table;
            std::vector<WasmException> exceptions;
            std::vector<WasmElemSegment> elem_segments;
            std::vector<WasmCompilationHint> compilation_hints;
            SignatureMap signature_map; // canonicalizing map for signature indexes.

            ModuleOrigin origin = kWasmOrigin; // origin of the module
            mutable std::unique_ptr<std::unordered_map<uint32_t, WireBytesRef>>
                function_names;
            std::string source_map_url;

            explicit WasmModule(std::unique_ptr<Zone> signature_zone = nullptr);

            WireBytesRef LookupFunctionName(const ModuleWireBytes& wire_bytes,
                uint32_t function_index) const;
            void AddFunctionNameForTesting(int function_index, WireBytesRef name);
        };

        size_t EstimateStoredSize(const WasmModule* module);

        // Interface to the storage (wire bytes) of a wasm module.
        // It is illegal for anyone receiving a ModuleWireBytes to store pointers based
        // on module_bytes, as this storage is only guaranteed to be alive as long as
        // this struct is alive.
        struct V8_EXPORT_PRIVATE ModuleWireBytes {
            explicit ModuleWireBytes(Vector<const byte> module_bytes)
                : module_bytes_(module_bytes)
            {
            }
            ModuleWireBytes(const byte* start, const byte* end)
                : module_bytes_(start, static_cast<int>(end - start))
            {
                DCHECK_GE(kMaxInt, end - start);
            }

            // Get a string stored in the module bytes representing a name.
            WasmName GetNameOrNull(WireBytesRef ref) const;

            // Get a string stored in the module bytes representing a function name.
            WasmName GetNameOrNull(const WasmFunction* function,
                const WasmModule* module) const;

            // Checks the given offset range is contained within the module bytes.
            bool BoundsCheck(uint32_t offset, uint32_t length) const
            {
                uint32_t size = static_cast<uint32_t>(module_bytes_.length());
                return offset <= size && length <= size - offset;
            }

            Vector<const byte> GetFunctionBytes(const WasmFunction* function) const
            {
                return module_bytes_.SubVector(function->code.offset(),
                    function->code.end_offset());
            }

            Vector<const byte> module_bytes() const { return module_bytes_; }
            const byte* start() const { return module_bytes_.start(); }
            const byte* end() const { return module_bytes_.end(); }
            size_t length() const { return module_bytes_.length(); }

        private:
            Vector<const byte> module_bytes_;
        };

        // A helper for printing out the names of functions.
        struct WasmFunctionName {
            WasmFunctionName(const WasmFunction* function, WasmName name)
                : function_(function)
                , name_(name)
            {
            }

            const WasmFunction* function_;
            const WasmName name_;
        };

        std::ostream& operator<<(std::ostream& os, const WasmFunctionName& name);

        // Get the debug info associated with the given wasm object.
        // If no debug info exists yet, it is created automatically.
        Handle<WasmDebugInfo> GetDebugInfo(Handle<JSObject> wasm);

        V8_EXPORT_PRIVATE MaybeHandle<WasmModuleObject> CreateModuleObjectFromBytes(
            Isolate* isolate, const byte* start, const byte* end, ErrorThrower* thrower,
            ModuleOrigin origin, Handle<Script> asm_js_script,
            Vector<const byte> asm_offset_table);

        V8_EXPORT_PRIVATE bool IsWasmCodegenAllowed(Isolate* isolate,
            Handle<Context> context);

        V8_EXPORT_PRIVATE Handle<JSArray> GetImports(Isolate* isolate,
            Handle<WasmModuleObject> module);
        V8_EXPORT_PRIVATE Handle<JSArray> GetExports(Isolate* isolate,
            Handle<WasmModuleObject> module);
        V8_EXPORT_PRIVATE Handle<JSArray> GetCustomSections(
            Isolate* isolate, Handle<WasmModuleObject> module, Handle<String> name,
            ErrorThrower* thrower);

        // Decode local variable names from the names section. Return FixedArray of
        // FixedArray of <undefined|String>. The outer fixed array is indexed by the
        // function index, the inner one by the local index.
        Handle<FixedArray> DecodeLocalNames(Isolate*, Handle<WasmModuleObject>);

        // TruncatedUserString makes it easy to output names up to a certain length, and
        // output a truncation followed by '...' if they exceed a limit.
        // Use like this:
        //   TruncatedUserString<> name (pc, len);
        //   printf("... %.*s ...", name.length(), name.start())
        template <int kMaxLen = 50>
        class TruncatedUserString {
            static_assert(kMaxLen >= 4, "minimum length is 4 (length of '...' plus one)");

        public:
            template <typename T>
            explicit TruncatedUserString(Vector<T> name)
                : TruncatedUserString(name.start(), name.length())
            {
            }

            TruncatedUserString(const byte* start, size_t len)
                : TruncatedUserString(reinterpret_cast<const char*>(start), len)
            {
            }

            TruncatedUserString(const char* start, size_t len)
                : start_(start)
                , length_(std::min(kMaxLen, static_cast<int>(len)))
            {
                if (len > static_cast<size_t>(kMaxLen)) {
                    memcpy(buffer_, start, kMaxLen - 3);
                    memset(buffer_ + kMaxLen - 3, '.', 3);
                    start_ = buffer_;
                }
            }

            const char* start() const { return start_; }

            int length() const { return length_; }

        private:
            const char* start_;
            const int length_;
            char buffer_[kMaxLen];
        };

    } // namespace wasm
} // namespace internal
} // namespace v8

#endif // V8_WASM_WASM_MODULE_H_
